/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.ssa;

import java.util.EnumSet;

import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.TranslationAdvice;
import com.android.dx.ssa.back.LivenessAnalyzer;
import com.android.dx.ssa.back.SsaToRop;

/**
 * Runs a method through the SSA form conversion, any optimization algorithms,
 * and returns it to rop form.
 */
public class Optimizer {

	private static boolean preserveLocals = true;

	private static TranslationAdvice advice;

	/** optional optimizer steps */
	public enum OptionalStep {
		MOVE_PARAM_COMBINER,
		SCCP,
		LITERAL_UPGRADE,
		CONST_COLLECTOR,
		ESCAPE_ANALYSIS
	}

	/**
	 * @return true if local variable information should be preserved, even at
	 *         code size/register size cost
	 */
	public static boolean getPreserveLocals() {
		return preserveLocals;
	}

	/**
	 * @return {@code non-null;} translation advice
	 */
	public static TranslationAdvice getAdvice() {
		return advice;
	}

	/**
	 * Runs optimization algorthims over this method, and returns a new instance
	 * of RopMethod with the changes.
	 * 
	 * @param rmeth
	 *            method to process
	 * @param paramWidth
	 *            the total width, in register-units, of this method's
	 *            parameters
	 * @param isStatic
	 *            true if this method has no 'this' pointer argument.
	 * @param inPreserveLocals
	 *            true if local variable info should be preserved, at the cost
	 *            of some registers and insns
	 * @param inAdvice
	 *            {@code non-null;} translation advice
	 * @return optimized method
	 */
	public static RopMethod optimize(RopMethod rmeth, int paramWidth,
			boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice) {

		return optimize(rmeth, paramWidth, isStatic, inPreserveLocals,
				inAdvice, EnumSet.allOf(OptionalStep.class));
	}

	/**
	 * Runs optimization algorthims over this method, and returns a new instance
	 * of RopMethod with the changes.
	 * 
	 * @param rmeth
	 *            method to process
	 * @param paramWidth
	 *            the total width, in register-units, of this method's
	 *            parameters
	 * @param isStatic
	 *            true if this method has no 'this' pointer argument.
	 * @param inPreserveLocals
	 *            true if local variable info should be preserved, at the cost
	 *            of some registers and insns
	 * @param inAdvice
	 *            {@code non-null;} translation advice
	 * @param steps
	 *            set of optional optimization steps to run
	 * @return optimized method
	 */
	public static RopMethod optimize(RopMethod rmeth, int paramWidth,
			boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice, EnumSet<OptionalStep> steps) {
		SsaMethod ssaMeth = null;

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic);
		runSsaFormSteps(ssaMeth, steps);

		RopMethod resultMeth = SsaToRop.convertToRopMethod(ssaMeth, false);

		if (resultMeth.getBlocks().getRegCount() > advice
				.getMaxOptimalRegisterCount()) {
			// Try to see if we can squeeze it under the register count bar
			resultMeth = optimizeMinimizeRegisters(rmeth, paramWidth, isStatic,
					steps);
		}
		return resultMeth;
	}

	/**
	 * Runs the optimizer with a strategy to minimize the number of rop-form
	 * registers used by the end result. Dex bytecode does not have instruction
	 * forms that take register numbers larger than 15 for all instructions. If
	 * we've produced a method that uses more than 16 registers, try again with
	 * a different strategy to see if we can get under the bar. The end result
	 * will be much more efficient.
	 * 
	 * @param rmeth
	 *            method to process
	 * @param paramWidth
	 *            the total width, in register-units, of this method's
	 *            parameters
	 * @param isStatic
	 *            true if this method has no 'this' pointer argument.
	 * @param steps
	 *            set of optional optimization steps to run
	 * @return optimized method
	 */
	private static RopMethod optimizeMinimizeRegisters(RopMethod rmeth,
			int paramWidth, boolean isStatic, EnumSet<OptionalStep> steps) {
		SsaMethod ssaMeth;
		RopMethod resultMeth;

		ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic);

		EnumSet<OptionalStep> newSteps = steps.clone();

		/*
		 * CONST_COLLECTOR trades insns for registers, which is not an
		 * appropriate strategy here.
		 */
		newSteps.remove(OptionalStep.CONST_COLLECTOR);

		runSsaFormSteps(ssaMeth, newSteps);

		resultMeth = SsaToRop.convertToRopMethod(ssaMeth, true);
		return resultMeth;
	}

	private static void runSsaFormSteps(SsaMethod ssaMeth,
			EnumSet<OptionalStep> steps) {
		boolean needsDeadCodeRemover = true;

		if (steps.contains(OptionalStep.MOVE_PARAM_COMBINER)) {
			MoveParamCombiner.process(ssaMeth);
		}

		if (steps.contains(OptionalStep.SCCP)) {
			SCCP.process(ssaMeth);
			DeadCodeRemover.process(ssaMeth);
			needsDeadCodeRemover = false;
		}

		if (steps.contains(OptionalStep.LITERAL_UPGRADE)) {
			LiteralOpUpgrader.process(ssaMeth);
			DeadCodeRemover.process(ssaMeth);
			needsDeadCodeRemover = false;
		}

		/*
		 * ESCAPE_ANALYSIS impacts debuggability, so left off by default
		 */
		steps.remove(OptionalStep.ESCAPE_ANALYSIS);
		if (steps.contains(OptionalStep.ESCAPE_ANALYSIS)) {
			EscapeAnalysis.process(ssaMeth);
			DeadCodeRemover.process(ssaMeth);
			needsDeadCodeRemover = false;
		}

		if (steps.contains(OptionalStep.CONST_COLLECTOR)) {
			ConstCollector.process(ssaMeth);
			DeadCodeRemover.process(ssaMeth);
			needsDeadCodeRemover = false;
		}

		// dead code remover must be run before phi type resolver
		if (needsDeadCodeRemover) {
			DeadCodeRemover.process(ssaMeth);
		}

		PhiTypeResolver.process(ssaMeth);
	}

	public static SsaMethod debugEdgeSplit(RopMethod rmeth, int paramWidth,
			boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice) {

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		return SsaConverter.testEdgeSplit(rmeth, paramWidth, isStatic);
	}

	public static SsaMethod debugPhiPlacement(RopMethod rmeth, int paramWidth,
			boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice) {

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		return SsaConverter.testPhiPlacement(rmeth, paramWidth, isStatic);
	}

	public static SsaMethod debugRenaming(RopMethod rmeth, int paramWidth,
			boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice) {

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		return SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic);
	}

	public static SsaMethod debugDeadCodeRemover(RopMethod rmeth,
			int paramWidth, boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice) {

		SsaMethod ssaMeth;

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic);
		DeadCodeRemover.process(ssaMeth);

		return ssaMeth;
	}

	public static SsaMethod debugNoRegisterAllocation(RopMethod rmeth,
			int paramWidth, boolean isStatic, boolean inPreserveLocals,
			TranslationAdvice inAdvice, EnumSet<OptionalStep> steps) {

		SsaMethod ssaMeth;

		preserveLocals = inPreserveLocals;
		advice = inAdvice;

		ssaMeth = SsaConverter.convertToSsaMethod(rmeth, paramWidth, isStatic);

		runSsaFormSteps(ssaMeth, steps);

		LivenessAnalyzer.constructInterferenceGraph(ssaMeth);

		return ssaMeth;
	}
}
